package client;

import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
import java.net.*;
import java.io.*;
import msgs.*;
import java.util.*;
import org.jdesktop.jdic.tray.*;

import javax.sound.sampled.*;

import java.util.*;

/**
 * Brings up the TigerTalk client GUI window, allows for login and network selection
 * Team 9: Andrew Hayworth, Brian Parrella, Ryan Kortmann, Nina Papa
 * @author Andrew Hayworth
 */

// Had to comment out tray code last minute -- something broke!! :-(

public class TTClient implements TTConstants {
	protected static boolean soundOk = true;
	private JComboBox jcb;
	private String[] ntwks = { "TigerTalk", "AOL Instant Messenger" };
	private JFrame blWindow;
	private JTextField uNameField, hostnameField;
	private JPasswordField pwdField;
	private JLabel uNameLabel, pwdLabel, hostnameLabel;
	private JPanel jp1, jp2, jp3, jp4, jp5, jp6;
	private JButton login, cancel;
	private int p1, p2;
	
	/**
	 * logout menu item, we need to access this from a few places
	 */
	protected static JMenuItem miLogout;

	/**
	 * System tray icon
	 */
	//protected static org.jdesktop.jdic.tray.TrayIcon trayIcon; // use the fully qualified package name as not to confuse JDK's newer than 5
	
	/**
	 * Server ip address
	 */
	protected static String server;
	
	/**
	 * Our username
	 */
	protected static String name;
	
	/**
	 * Tigertalk login authentication key
	 */
	protected static byte[] key;
	
	/**
	 * Our IP address
	 */
	protected static String ip;
	
	/**
	 * Our password. 
	 */
	protected static String pwd;
	
	/**
	 * Buddylist data
	 */
	protected static String[] blData;
	
	/**
	 * Status of each buddy in the list
	 */
	protected static HashMap<String, Integer> blDataStatus;
	
	/**
	 * Boolean to determine if the TTRunningClient should update it's buddylist
	 */
	protected static boolean updateBLNeeded = false;
	
	/**
	 * Running list of all the group chats
	 */
	protected static HashMap<String, Chat> chats;
	
	/**
	 * Running list of the private messages
	 */
	protected static HashMap<String, PMChat> pmChats;
	
	/**
	 * Determines if we were in the middle of updating our profile
	 */
	protected static boolean updatingProfile = false;
	
	/**
	 * Locations of our sound files
	 */
	protected static URL loginSound, logoutSound, msgInSound, msgOutSound;
	
	// this is a work-around table of os'es that tend to report that their external ip address is localhost...
	private ArrayList<String> ipWorkaroundTable;

	public static void main(String[] args) {
		new TTClient();
	}
	
	// forcing one to read my gui code is forbidden by the geneva convention
	public TTClient() {
		
		System.out.println("Running on " + System.getProperty("os.name"));
		
		// Some OS X specific gui niceness here
		if (System.getProperty("os.name").startsWith("Mac")) {
			try {
				UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
				System.setProperty("apple.laf.useScreenMenuBar", "true");
				System.setProperty("com.apple.mrj.application.apple.menu.about.name", "TigerTalk");
				System.setProperty("com.apple.mrj.application.growbox.intrudes", "false");
				
			}
			catch (Exception e) {e.printStackTrace();} // this is ok, because on exception it just defaults to swing l&f
		}
		
		ipWorkaroundTable = createWorkaroundTable();
		loginSound = getClass().getResource("sound/login.wav");
		logoutSound = getClass().getResource("sound/logout.wav");
		msgInSound = getClass().getResource("sound/msgin.wav");
		msgOutSound = getClass().getResource("sound/msgout.wav");

		pmChats = new HashMap<String, PMChat>();
		chats = new HashMap<String, Chat>();
		
		// where we're connecting on. Server can be run on different ports but the client, by default, cannot.
		p1 = 6666;
		p2 = 6667;
		
		blDataStatus = new HashMap<String, Integer>();

		blWindow = new JFrame();
		uNameLabel = new JLabel("Username");
		uNameField = new JTextField(10);
		pwdLabel = new JLabel("Password");
		pwdField = new JPasswordField(10);
		hostnameField = new JTextField(10);
		hostnameField.setText("localhost");
		hostnameLabel = new JLabel("Server");
		login = new JButton("Login");
		login.setMnemonic('L');
		cancel = new JButton("Cancel");
		cancel.setMnemonic('C');
		jcb = new JComboBox(ntwks);

		blWindow.setLayout(new BoxLayout(blWindow.getContentPane(), BoxLayout.Y_AXIS));
		
		jp2 = new JPanel();
		jp3 = new JPanel();
		jp4 = new JPanel();
		jp5 = new JPanel();
		jp6 = new JPanel();
		
		jp2.setMaximumSize(new Dimension(210,20));
		jp3.setMaximumSize(new Dimension(210,20));
		jp4.setMaximumSize(new Dimension(210,20));
		jp5.setMaximumSize(new Dimension(210,20));
		jp6.setMaximumSize(new Dimension(210,20));

		jp2.add(uNameLabel);
		jp2.add(uNameField);
		jp3.add(pwdLabel);
		jp3.add(pwdField);
		jp4.add(hostnameLabel);
		jp4.add(hostnameField);
		jp5.add(jcb);
		jp6.add(login);
		jp6.add(cancel);
		blWindow.add(Box.createVerticalGlue());
		blWindow.add(jp2);
		blWindow.add(jp3);
		blWindow.add(jp4);
		blWindow.add(jp5);
		blWindow.add(jp6);
		blWindow.add(Box.createVerticalGlue());

		blWindow.setSize(250, 600);

		blWindow.setLocationRelativeTo(null);
		blWindow.setTitle("TigerTalk v1.0 -- Login");
		blWindow.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		blWindow.setVisible(true);
		
		createTrayIcon();
		
		///////////
		//// HERE BE KEYLISTENERS
		///////////
		login.addKeyListener(new KeyAdapter() {
			public void keyReleased(KeyEvent ke) {
				if (ke.getKeyCode() == KeyEvent.VK_ENTER) {
					login.doClick();
				}
			}
		});
		
		jcb.addKeyListener(new KeyAdapter() {
			public void keyReleased(KeyEvent ke) {
				if (ke.getKeyCode() == KeyEvent.VK_ENTER) {
					login.doClick();
				}
			}
		});
		
		cancel.addKeyListener(new KeyAdapter() {
			public void keyReleased(KeyEvent ke) {
				if (ke.getKeyCode() == KeyEvent.VK_ENTER) {
					cancel.doClick();
				}
			}
		});
		
		uNameField.addKeyListener(new KeyAdapter() {
			public void keyReleased(KeyEvent ke) {
				if (ke.getKeyCode() == KeyEvent.VK_ENTER) {
					login.doClick();
				}
			}
		});
		
		pwdField.addKeyListener(new KeyAdapter() {
			public void keyReleased(KeyEvent ke) {
				if (ke.getKeyCode() == KeyEvent.VK_ENTER) {
					login.doClick();
				}
			}
		});
		
		hostnameField.addKeyListener(new KeyAdapter() {
			public void keyReleased(KeyEvent ke) {
				if (ke.getKeyCode() == KeyEvent.VK_ENTER) {
					login.doClick();
				}
			}
		});
		
		// sets the hostname field to aol or localhost depending on the selection in jcb
		// hackish, but it works :)
		jcb.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent ae) {
				if (jcb.getSelectedItem().toString().equals("AOL Instant Messenger")) {
					jp4.setVisible(false);
					hostnameField.setText("aol");
				}
				else {
					jp4.setVisible(true);
					hostnameField.setText("localhost");
				}
			}
		});
		
		// this starts the login process
		login.addActionListener( new ActionListener() {
			public void actionPerformed(ActionEvent ae) {
				try {
					if (hostnameField.getText().equals("aol")) {
						String p = "";
						for (char c : pwdField.getPassword()) {
							p += c;
						}
						// if we're trying to do aol, gtfo of this class and start AIM
						(new AIM(uNameField.getText(), p)).start();
						blWindow.dispose();
						return;
					}
					// otherwise, try to raise a ttserver
					login();
					
					// provided we logged in, start our processing queues
					startServerListener();
					startServerSender();
					startMsgParser();
					
					PMSender ps = new PMSender(1234);
					ps.start();
					
					PMListener pl = new PMListener(1234);
					pl.start();
					
					PMParser pmp = new PMParser();
					pmp.start();
					
					blWindow.dispose();
					// start the main show!
					(new TTRunningClient(blData)).start();
				}
				catch (LoginException e) {
					JOptionPane.showMessageDialog(null, e.getMessage(), "Login failed!", JOptionPane.ERROR_MESSAGE);
				}
			}

		});
		
		cancel.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent ae) {
				System.exit(0);
			}
		});
		
	}
	// This exists solely that I can have a nice table of OS'es that we need to hack the ip address for...
	private ArrayList<String> createWorkaroundTable() {
		ArrayList<String> a = new ArrayList<String>();
		// essentially, we only need to add the unices, as java returns correct IP addr info for NT platforms and OS X.
		// i'm not sure if it's a bug or feature that we're working around, but it is sure annoying.
		a.add("Linux");
		a.add("Solaris");
		a.add("SunOS");
		a.add("MPE/iX");
		a.add("HP-UX");
		a.add("HP UX");
		a.add("AIX");
		a.add("FreeBSD");
		a.add("Irix");
		a.add("Digital Unix");
		return a;
	}

	// some convience methods for starting listeners ...
	// originally created with the hopes that they would do more complex setup, but they never really did.
	protected void startServerSender() {
		(new ServerSender(p1)).start();
	}

	protected void startServerListener() {
		(new ServerListener(p2)).start();
		
	}
	
	private void startMsgParser() {
		(new MsgParser()).start();
		
	}

	// our private login method. handles getting the ip address, logging into the server, raising exceptions on failure and 
	// setting up our initial environment
	private void login() throws LoginException{
		try {
			server = hostnameField.getText();
			
			// open a socket and server socket to talk to TigerTalk
			Socket sock = new Socket(server, p1);
			ServerSocket ssock = new ServerSocket(p2);
			
			// try to get the ip address ....if we're on a bad os :(
			if (ipWorkaroundTable.contains(System.getProperty("os.name"))) {
				System.out.println("Running on a unix, trying to get proper IP address info..");
				// this is really braindamaged.... but its the easiest way...
				Enumeration<NetworkInterface> e = NetworkInterface.getNetworkInterfaces();
				while (e.hasMoreElements()) {
					NetworkInterface n = (NetworkInterface) e.nextElement();
					// Apple, I truly hate thee. Why must thou ship JDK 1.5? 
					// Thou eliminatest many convenient methods from mine aresenal...
					//if (!(n.isLoopback()) && n.isUp()) {
					Enumeration<InetAddress> ei = n.getInetAddresses();
					while (ei.hasMoreElements()) {
						InetAddress i = (InetAddress) ei.nextElement();		
						if (!(i.isLoopbackAddress())) {
							if (!(Character.isDigit(i.getHostAddress().charAt(0)))) System.out.println("IPv6 skipped");
							else {
								ip = i.getHostAddress();
								System.out.println("Detected IP address: " + ip);
							}
						}
					}
				}
			}
			// yay, we're not on a bad os, this is simple now!
			else {
				InetAddress inetAddr = InetAddress.getLocalHost();
				ip = inetAddr.getHostAddress();
				System.out.println("Detected IP address: " + ip);
			}

			String p = "";
			for (char c : pwdField.getPassword()) {
				p += c;
			}
			
			// set up our login request message
			ClientMsg cm = new ClientMsg(ip, uNameField.getText(), p);
			cm.setAction(Actions.LOGIN);
			ObjectOutputStream oos = new ObjectOutputStream(sock.getOutputStream());
			oos.writeObject(cm);	//sendit
			oos.flush();
			oos.close();
			
			// wait for a response
			sock = ssock.accept();
			ObjectInputStream ois = new ObjectInputStream(sock.getInputStream());
			cm = (ClientMsg) ois.readObject();
			ois.close();
			sock.close();
			ssock.close();
			
			// if no go, throw a login exception back up to the actionlistener on the login button, and let them try again
			if (cm.getResponse() == Responses.DENY) {
				throw new LoginException("Username or password incorrect.");	
			}
			
			// otherwise, setup our initial variables
			name = cm.getName();
			key = cm.getClientKey();
			pwd = cm.getPwd();
			String[] s = { "" };
			blData = s;
			blDataStatus.put("", 0);
		}
		catch (IOException e) {
			throw new LoginException("Unable to contact server.");
		}
		catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Plays the login sound
	 */
	public static void playLogin() {
		if (soundOk) {
			try {
				Clip soundClip = AudioSystem.getClip();
				AudioInputStream ais = AudioSystem.getAudioInputStream(TTClient.loginSound);
				soundClip.open(ais);
				soundClip.start();
			}
			catch (Exception e) { e.printStackTrace(); }
		}
	}
	
	/**
	 * Plays the logout sound
	 */
	public static void playLogout() {
		if (soundOk) {
			try {
				Clip soundClip = AudioSystem.getClip();
				AudioInputStream ais = AudioSystem.getAudioInputStream(TTClient.logoutSound);
				soundClip.open(ais);
				soundClip.start();
			}
			catch (Exception e) { e.printStackTrace(); }
		}
	}
	
	/**
	 * Plays the message in sound
	 */
	public static void playMsgIn() {
		if (soundOk) {
			try { 
				Clip soundClip = AudioSystem.getClip();
				AudioInputStream ais = AudioSystem.getAudioInputStream(TTClient.msgInSound);
				soundClip.open(ais);
				soundClip.start();
			}
			catch (Exception e) { e.printStackTrace(); }
		}
	}
	
	/**
	 * Plays the message out sound
	 */
	public static void playMsgOut() {
		if (soundOk) {
			try {
				Clip soundClip = AudioSystem.getClip();
				AudioInputStream ais = AudioSystem.getAudioInputStream(TTClient.msgOutSound);
				soundClip.open(ais);
				soundClip.start();
			}
			catch (Exception e) { e.printStackTrace(); }
		}
	}
	
	/** 
	 * This creates our nifty little tray icon!
	 */
	public void createTrayIcon()
	{
       
        JPopupMenu pmMenu = new JPopupMenu();
        
        JMenuItem miExit = new JMenuItem("Exit");
        JMenuItem miAbout = new JMenuItem("About");
        miLogout = new JMenuItem("Logout");
        miLogout.setEnabled(false);
        
        pmMenu.add(miAbout);
        pmMenu.addSeparator();
        pmMenu.add(miLogout);
        pmMenu.add(miExit);
        
        // will this work in a jar??
        Image iconImage = Toolkit.getDefaultToolkit().getImage(getClass().getResource("images/tigertalk.png"));
        ImageIcon iic = new ImageIcon(iconImage);
        
        // Have to package this into an icon, or we can't use the JDIC
        //trayIcon = new TrayIcon((Icon)iic, "TigerTalk v1.0", pmMenu);
       // trayIcon = new org.jdesktop.jdic.tray.TrayIcon((Icon)iic, "TigerTalk v1.0", pmMenu);
       // trayIcon.setIconAutoSize(true);
        
        
        // kills things off cleanly if they try to close the program from this menu
        miExit.addActionListener(new ActionListener()
        {
        	public void actionPerformed(ActionEvent ae)
        	{
        		if (name != null ) {
        			System.out.println("Shutting down!");
        			ServerSender.killServerSender();
    				ServerListener.killServerListener();
    				MsgParser.killMsgParser();
    				PMListener.killPMListener();
    				PMSender.killPMSender();
    				PMParser.killPMParser();
        		}
        		System.exit(0);
        	}
        });
        
        miAbout.addActionListener(new ActionListener()
        {
        	public void actionPerformed(ActionEvent ae)
        	{
        		//JDIC doesn't support this on the mac yet :'-(
        		if (System.getProperty("os.name").startsWith("Mac")) {
        			JOptionPane.showMessageDialog(null, "TigerTalk v1.0", "About TigerTalk", JOptionPane.INFORMATION_MESSAGE);
        		}
        		else {
        //			trayIcon.displayMessage("About TigerTalk v1.0", "219 Final Project\nMay 21st, 2009\nInfo box or this kind of message box for the about screen???", org.jdesktop.jdic.tray.TrayIcon.INFO_MESSAGE_TYPE);
        		}
        		//JOptionPane.showMessageDialog(null, "TigerTalk v1.0", "About TigerTalk", JOptionPane.INFORMATION_MESSAGE);
        	}
        });
        
        
        // using full classnames. Here's the reason:
        // JDK 6 has native, fullfeatured classes for SystemTray and TrayIcon. Work wonderfully!
        // ... but OS X ships JDK5. Which has neither class. In any way shape or form.
        // So, we found an old library that the JDK 6 features were *based* from. It works rather well, and is cross platform 
        // even on an alpha build, which we are using, it is stable.
        // however, to avoid confusing a JDK6 compiler with identical classnames, we're fully specifying them here.
        // It's the same situation as in java.util.Timer vs javax.swing.Timer -- just ambiguous
     //   org.jdesktop.jdic.tray.SystemTray tray = org.jdesktop.jdic.tray.SystemTray.getDefaultSystemTray();
        try
        {
    //        tray.addTrayIcon(trayIcon);
        }
        catch (Exception e)
        {
            System.out.println("TrayIcon could not be added.");
        }
        
	}

	/**
	 * @return the soundOk
	 */
	public static boolean isSoundOk() {
		return soundOk;
	}

	/**
	 * @param soundOk the soundOk to set
	 */
	public static void setSoundOk(boolean soundOk) {
		TTClient.soundOk = soundOk;
	}
	
	
}

/**
 * This class just allows us to barf up an error to the main gui when logging in
 * @author Andrew Hayworth
 */
class LoginException extends Exception {
	
	public LoginException() {
		super();
	}
	
	public LoginException(String cause) {
		super(cause);
	}
}